/*
 * Copyright (C) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "SVGPathParser.h"
#include <native_drawing/drawing_path.h>
#include <stdexcept>
#include <sstream>
#include <cmath>
#include <cctype>
#include "cmath"
#include "vector"


namespace rnoh {
float SVGPathParser::mScale = 0.0f;
int SVGPathParser::i = 0;
int SVGPathParser::l = 0;
std::string SVGPathParser::s = "";
float SVGPathParser::mPenX = 0.0f;
float SVGPathParser::mPenY = 0.0f;
float SVGPathParser::mPivotX = 0.0f;
float SVGPathParser::mPivotY = 0.0f;
float SVGPathParser::mPenDownX = 0.0f;
float SVGPathParser::mPenDownY = 0.0f;
bool SVGPathParser::mPenDown = false;

OH_Drawing_Path *SVGPathParser::parse(const std::string d)
{
    if (d.empty()) {
        return nullptr;
    }
    OH_Drawing_Path *cPath_ = OH_Drawing_PathCreate();
    char prevCmd = ' ';
    l = d.length();
    s = d;
    i = 0;

    mPenX = 0.f;
    mPenY = 0.f;
    mPivotX = 0.f;
    mPivotY = 0.f;
    mPenDownX = 0.f;
    mPenDownY = 0.f;
    mPenDown = false;

    while (i < l) {
        SkipSpaces();
        if (i >= l) {
            break;
        }
        bool has_prev_cmd = prevCmd != ' ';
        char first_char = s.at(i);

        if (!has_prev_cmd && first_char != 'M' && first_char != 'm') {
            // The first segment must be a MoveTo.
            std::ostringstream oss;
            oss << "Unexpected character" << first_char << ",i=" << i << ",s=" << s;
            throw std::logic_error(oss.str());
        }

        bool is_implicit_move_to;
        char cmd;
        if (IsCmd(first_char)) {
            is_implicit_move_to = false;
            cmd = first_char;
            i += 1;
        } else if (is_number_start(first_char) && has_prev_cmd) {
            if (prevCmd == 'Z' || prevCmd == 'z') {
                // ClosePath cannot be followed by a number.
                std::ostringstream oss;
                oss << "Unexpected number after 'z' (s=" << s << ")";
                throw std::logic_error(oss.str());
            }

            if (prevCmd == 'M' || prevCmd == 'm') {
                // 'If a moveto is followed by multiple pairs of coordinates,
                // the subsequent pairs are treated as implicit lineto commands.'
                // So we parse them as LineTo.
                is_implicit_move_to = true;
                if (is_absolute(prevCmd)) {
                    cmd = 'L';
                } else {
                    cmd = 'l';
                }
            } else {
                is_implicit_move_to = false;
                cmd = prevCmd;
            }
        } else {
            std::ostringstream oss;
            oss << "Unexpected character" << first_char << ",i=" << i << ",s=" << s;
            throw std::logic_error(oss.str());
        }
        bool absolute = is_absolute(cmd);
        switch (cmd) {
            case 'm': {
                rnoh:
                    move(parse_list_number(), parse_list_number(), cPath_);
                    break;
            }
            case 'M': {
                moveTo(parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'l': {
                line(parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'L': {
                lineTo(parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'h': {
                line(parse_list_number(), 0, cPath_);
                break;
            }
            case 'H': {
                lineTo(parse_list_number(), mPenY, cPath_);
                break;
            }
            case 'v': {
                line(0, parse_list_number(), cPath_);
                break;
            }
            case 'V': {
                lineTo(mPenX, parse_list_number(), cPath_);
                break;
            }
            case 'c': {
                curve(parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number(),
                    parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'C': {
                curveTo(parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number(),
                    parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 's': {
                smoothCurve(parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'S': {
                smoothCurveTo(parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number(),
                    cPath_);
                break;
            }
            case 'q': {
                quadraticBezierCurve(parse_list_number(), parse_list_number(), parse_list_number(), parse_list_number(),
                    cPath_);
                break;
            }
            case 'Q': {
                quadraticBezierCurveTo(parse_list_number(), parse_list_number(), parse_list_number(),
                    parse_list_number(), cPath_);
                break;
            }
            case 't': {
                smoothQuadraticBezierCurve(parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'T': {
                smoothQuadraticBezierCurveTo(parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'a': {
                arc(parse_list_number(), parse_list_number(), parse_list_number(), parse_flag(), parse_flag(),
                    parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'A': {
                arcTo(parse_list_number(), parse_list_number(), parse_list_number(), parse_flag(), parse_flag(),
                    parse_list_number(), parse_list_number(), cPath_);
                break;
            }
            case 'z':
            case 'Z': {
                close(cPath_);
                break;
            }
            default: {
                std::ostringstream oss;
                oss << "Unexpected comand " << cmd << ",s=" << s << ")";
                throw std::logic_error(oss.str());
            }
        }

        if (is_implicit_move_to) {
            if (absolute) {
                prevCmd = 'M';
            } else {
                prevCmd = 'm';
            }
        } else {
            prevCmd = cmd;
        }
    }
    return cPath_;
}

void SVGPathParser::move(float x, float y, OH_Drawing_Path *cPath_)
{
    moveTo(x + mPenX, y + mPenY, cPath_);
}

void SVGPathParser::moveTo(float x, float y, OH_Drawing_Path *cPath_)
{
    mPenDownX = mPivotX = mPenX = x;
    mPenDownY = mPivotY = mPenY = y;
    OH_Drawing_PathMoveTo(cPath_, x * mScale, y * mScale);
}

void SVGPathParser::line(float x, float y, OH_Drawing_Path *cPath_)
{
    lineTo(x + mPenX, y + mPenY, cPath_);
}
void SVGPathParser::lineTo(float x, float y, OH_Drawing_Path *cPath_)
{
    SetPenDown();
    mPivotX = mPenX = x;
    mPivotY = mPenY = y;
    OH_Drawing_PathLineTo(cPath_, x * mScale, y * mScale);
}

void SVGPathParser::curve(float c1x, float c1y, float c2x, float c2y, float ex, float ey, OH_Drawing_Path *cPath_)
{
    curveTo(c1x + mPenX, c1y + mPenY, c2x + mPenX, c2y + mPenY, ex + mPenX, ey + mPenY, cPath_);
}

void SVGPathParser::curveTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey, OH_Drawing_Path *cPath_)
{
    mPivotX = c2x;
    mPivotY = c2y;
    cubicTo(c1x, c1y, c2x, c2y, ex, ey, cPath_);
}

void SVGPathParser::cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, float ey, OH_Drawing_Path *cPath_)
{
    SetPenDown();
    mPenX = ex;
    mPenY = ey;
    OH_Drawing_PathCubicTo(cPath_, c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale, ey * mScale);
}


void SVGPathParser::smoothCurve(float c1x, float c1y, float ex, float ey, OH_Drawing_Path *cPath_)
{
    smoothCurveTo(c1x + mPenX, c1y + mPenY, ex + mPenX, ey + mPenY, cPath_);
}
void SVGPathParser::smoothCurveTo(float c1x, float c1y, float ex, float ey, OH_Drawing_Path *cPath_)
{
    float c2x = c1x;
    float c2y = c1y;
    int count2 = 2;
    c1x = (mPenX * count2) - mPivotX;
    c1y = (mPenY * count2) - mPivotY;
    mPivotX = c2x;
    mPivotY = c2y;
    cubicTo(c1x, c1y, c2x, c2y, ex, ey, cPath_);
}

void SVGPathParser::quadraticBezierCurve(float c1x, float c1y, float c2x, float c2y, OH_Drawing_Path *cPath_)
{
    quadraticBezierCurveTo(c1x + mPenX, c1y + mPenY, c2x + mPenX, c2y + mPenY, cPath_);
}

void SVGPathParser::quadraticBezierCurveTo(float c1x, float c1y, float c2x, float c2y, OH_Drawing_Path *cPath_)
{
    int count2 = 2;
    int count3 = 3;
    mPivotX = c1x;
    mPivotY = c1y;
    float ex = c2x;
    float ey = c2y;
    c2x = (ex + c1x * count2) / count3;
    c2y = (ey + c1y * count2) / count3;
    c1x = (mPenX + c1x * count2) / count3;
    c1y = (mPenY + c1y * count2) / count3;
    cubicTo(c1x, c1y, c2x, c2y, ex, ey, cPath_);
}

void SVGPathParser::smoothQuadraticBezierCurve(float c1x, float c1y, OH_Drawing_Path *cPath_)
{
    smoothQuadraticBezierCurveTo(c1x + mPenX, c1y + mPenY, cPath_);
}

void SVGPathParser::smoothQuadraticBezierCurveTo(float c1x, float c1y, OH_Drawing_Path *cPath_)
{
    float c2x = c1x;
    float c2y = c1y;
    int count2 = 2;
    c1x = (mPenX * count2) - mPivotX;
    c1y = (mPenY * count2) - mPivotY;
    quadraticBezierCurveTo(c1x, c1y, c2x, c2y, cPath_);
}
void SVGPathParser::arc(float rx, float ry, float rotation, bool outer, bool clockwise, float x, float y,
    OH_Drawing_Path *cPath_)
{
    arcTo(rx, ry, rotation, outer, clockwise, x + mPenX, y + mPenY, cPath_);
}

void SVGPathParser::arcTo(float rx, float ry, float rotation, bool outer, bool clockwise, float x, float y,
    OH_Drawing_Path *cPath_)
{
    float tX = mPenX;
    float tY = mPenY;
    ry = abs(ry == 0 ? (rx == 0 ? (y - tY) : rx) : ry);
    rx = abs(rx == 0 ? (x - tX) : rx);
    if (rx == 0 || ry == 0 || (x == tX && y == tY)) {
        lineTo(x, y, cPath_);
        return;
    }

    float rad = static_cast<float>(rotation * (M_PI / 180.0));
    float cos = std::cos(rad);
    float sin = std::sin(rad);
    x -= tX;
    y -= tY;

    // Ellipse Center
    int count2 = 2;
    float cx = cos * x / count2 + sin * y / count2;
    float cy = -sin * x / count2 + cos * y / count2;
    float rxry = rx * rx * ry * ry;
    float rycx = ry * ry * cx * cx;
    float rxcy = rx * rx * cy * cy;
    float a = rxry - rxcy - rycx;

    if (a < 0) {
        a = std::sqrt(1 - a / rxry);
        rx *= a;
        ry *= a;
        cx = x / count2;
        cy = y / count2;
    } else {
        a = std::sqrt(a / (rxcy + rycx));

        if (outer == clockwise) {
            a = -a;
        }
        float cxd = -a * cy * rx / ry;
        float cyd = a * cx * ry / rx;
        cx = cos * cxd - sin * cyd + x / count2;
        cy = sin * cxd + cos * cyd + y / count2;
    }

    // Rotation + Scale Transform
    float xx = cos / rx;
    float yx = sin / rx;
    float xy = -sin / ry;
    float yy = cos / ry;

    // Start and End Angle
    float sa = std::atan2(xy * -cx + yy * -cy, xx * -cx + yx * -cy);
    float ea = std::atan2(xy * (x - cx) + yy * (y - cy), xx * (x - cx) + yx * (y - cy));

    cx += tX;
    cy += tY;
    x += tX;
    y += tY;

    SetPenDown();

    mPenX = mPivotX = x;
    mPenY = mPivotY = y;
    int count180 = 180;
    int count360 = 360;
    double point180 = 180.0;

    if (rx != ry || rad != 0.f) {
        arcToBezier(cx, cy, rx, ry, sa, ea, clockwise, rad, cPath_);
    } else {
        float start = static_cast<float>(sa * point180 / M_PI);
        float end = static_cast<float>(ea * point180 / M_PI);
        float sweep = abs((start - end) - std::floor((start - end) / count360) * count360);


        if (outer) {
            if (sweep < count180) {
                sweep = count360 - sweep;
            }
        } else {
            if (sweep > count180) {
                sweep = count360 - sweep;
            }
        }

        if (!clockwise) {
            sweep = -sweep;
        }
        OH_Drawing_PathArcTo(cPath_, (cx - rx) * mScale, (cy - rx) * mScale, (cx + rx) * mScale, (cy + rx) * mScale,
            start, sweep);
    }
}
void SVGPathParser::SetPenDown()
{
    if (!mPenDown) {
        mPenDownX = mPenX;
        mPenDownY = mPenY;
        mPenDown = true;
    }
}
void SVGPathParser::close(OH_Drawing_Path *cPath_)
{
    if (mPenDown) {
        mPenX = mPenDownX;
        mPenY = mPenDownY;
        mPenDown = false;
        OH_Drawing_PathClose(cPath_);
    }
}
void SVGPathParser::arcToBezier(float cx, float cy, float rx, float ry, float sa, float ea, bool clockwise, float rad,
    OH_Drawing_Path *cPath_)
{
    // Inverse Rotation + Scale Transform
    float cos = std::cos(rad);
    float sin = std::sin(rad);
    float xx = cos * rx;
    float yx = -sin * ry;
    float xy = sin * rx;
    float yy = cos * ry;

    // Bezier Curve Approximation
    float arc = ea - sa;
    int count2 = 2;
    int count4 = 4;
    double point3 = 3.0;
    if (arc < 0 && clockwise) {
        arc += M_PI * count2;
    } else if (arc > 0 && !clockwise) {
        arc -= M_PI * count2;
    }

    int n = static_cast<int>std::ceil(std::abs(round(arc / (M_PI / count2))));

    float step = arc / n;
    float k = static_cast<float>((count4 / point3) * std::tan(step / count4));
    float x = std::cos(sa);
    float y = std::sin(sa);

    for (int counti = 0; counti < n; counti++) {
        float cp1x = x - k * y;
        float cp1y = y + k * x;

        sa += step;
        float newX = std::cos(sa);
        float newY = std::sin(sa);

        float cp2x = newX + k * newY;
        float cp2y = newY - k * newX;

        float c1x = (cx + xx * cp1x + yx * cp1y);
        float c1y = (cy + xy * cp1x + yy * cp1y);
        float c2x = (cx + xx * cp2x + yx * cp2y);
        float c2y = (cy + xy * cp2x + yy * cp2y);
        float ex = (cx + xx * newX + yx * newY);
        float ey = (cy + xy * newX + yy * newY);
        OH_Drawing_PathCubicTo(cPath_, c1x * mScale, c1y * mScale, c2x * mScale, c2y * mScale, ex * mScale,
            ey * mScale);
    }
}

double SVGPathParser::_round(double val)
{
    double multiplier = pow(10, 4);
    return round(val * multiplier) / multiplier;
}

void SVGPathParser::SkipSpaces()
{
    while (i < l && std::isspace(s.at(i))) {
        i++;
    }
}
bool SVGPathParser::IsCmd(char c)
{
    switch (c) {
        case 'M':
        case 'm':
        case 'Z':
        case 'z':
        case 'L':
        case 'l':
        case 'H':
        case 'h':
        case 'V':
        case 'v':
        case 'C':
        case 'c':
        case 'S':
        case 's':
        case 'Q':
        case 'q':
        case 'T':
        case 't':
        case 'A':
        case 'a':
            return true;
        default:
            return false;
    }
    return false;
}


bool SVGPathParser::is_number_start(char c)
{
    return (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+';
}

bool SVGPathParser::is_absolute(char c)
{
    return std::isupper(c);
}
// By the SVG spec 'large-arc' and 'sweep' must contain only one char
// and can be written without any separators, e.g.: 10 20 30 01 10 20.

bool SVGPathParser::parse_flag()
{
    SkipSpaces();

    char c = s.at(i);
    switch (c) {
        case '0':
        case '1': {
            i += 1;
            if (i < l && s.at(i) == ',') {
                i += 1;
            }
            SkipSpaces();
            break;
        }
        default:
            std::ostringstream oss;
            oss << "Unexpected flag" << c << ",i=" << i << ",s=" << s;
            throw std::logic_error(oss.str());
    }

    return c == '1';
}


float SVGPathParser::parse_list_number()
{
    if (i == l) {
        std::ostringstream oss;
        oss << "Unexpected end (s=" << s << ")";
        throw std::logic_error(oss.str());
    }

    float n = parse_number();
    SkipSpaces();
    parse_list_separator();

    return n;
}

float SVGPathParser::parse_number()
{
    // Strip off leading whitespaces.
    SkipSpaces();

    if (i == l) {
        std::ostringstream oss;
        oss << "Unexpected end (s=" << s << ")";
        throw std::logic_error(oss.str());
    }

    int start = i;

    char c = s.at(i);

    // Consume sign.
    if (c == '-' || c == '+') {
        i += 1;
        c = s.at(i);
    }

    // Consume integer.
    if (c >= '0' && c <= '9') {
        SkipDigits();
        if (i < l) {
            c = s.at(i);
        }
    } else if (c != '.') {
        std::ostringstream oss;
        oss << "Invalid number formating character " << c << ",i=" << i << ",s=" << s;
        throw std::logic_error(oss.str());
    }

    // Consume fraction.
    if (c == '.') {
        i += 1;
        SkipDigits();
        if (i < l) {
            c = s.at(i);
        }
    }

    if ((c == 'e' || c == 'E') && i + 1 < l) {
        char c2 = s.at(i + 1);
        // Check for `em`/`ex`.
        if (c2 != 'm' && c2 != 'x') {
            i += 1;
            c = s.at(i);

            if (c == '+' || c == '-') {
                i += 1;
                SkipDigits();
            } else if (c >= '0' && c <= '9') {
                SkipDigits();
            } else {
                std::ostringstream oss;
                oss << "Invalid number formating character " << c << ",i=" << i << ",s=" << s;
                throw std::logic_error(oss.str());
            }
        }
    }
    std::string num = s.substr(start, i);
    float n = std::stof(num);
    // inf, nan, etc. are an error.
    if (std::isinf(n) || std::isnan(n)) {
        std::ostringstream oss;
        oss << "Invalid number" << num << ",start=" << start << ",i=" << i << ",s=" << s;
        throw std::logic_error(oss.str());
    }

    return n;
}

void SVGPathParser::parse_list_separator()
{
    if (i < l && s.at(i) == ',') {
        i += 1;
    }
}

void SVGPathParser::SkipDigits()
{
    while (i < l && std::isdigit(s.at(i))) {
        i++;
    }
}
}; // namespace rnoh